package com.practice.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * NIO server
 */
public class NIOServer {
	
	/**
	 * Selector of ServerSocketChannel.
	 */
	private Selector selector;
	
	private boolean stop;

	/**
	 * Initializes server by specified port.
	 * 
	 * @param port
	 * @throws IOException
	 */
	public void initServer(int port) throws IOException {
		// Creates a ServerSocketChannel.
		ServerSocketChannel serverChannel = ServerSocketChannel.open();
		// Sets the ServerSocketChannel to be non-blocking.
		serverChannel.configureBlocking(false);
		// Binds ServerSocket to port.
		serverChannel.socket().bind(new InetSocketAddress(port));
		// Creates a new selector.
		this.selector = Selector.open();
		/*
		 * Registers ServerSocketChannel with selector. OP_ACCEPT, here specifies that
		 * we want to listen for accept events, the events that occur when a
		 * new connection is made. This is the only kind of event that is appropriate
		 * for a ServerSocketChannel.
		 */
		serverChannel.register(selector, SelectionKey.OP_ACCEPT);
		
		stop = false;
	}

	/**
	 * Listen for selector, receive message and send echo.
	 * 
	 * @throws IOException
	 */
	public void listen() throws IOException {
		System.out.println("Server is listening.");
		// Main loop, check selector.
		while (!stop) {
			// This method blocks until at least one of the registered events occurs. When
			// one or more events occur, the select() method returns the number of events
			// that occurred.
			selector.select();
			// Gets a Set of the SelectionKey objects for which events have occurred.
			Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();
			while (ite.hasNext()) {
				SelectionKey key = ite.next();
				// Removes the processed SelectionKey from the set of selected keys.
				ite.remove();

				if (key.isAcceptable()) { // It's "accept" event of ServerSocketChannel.

					ServerSocketChannel server = (ServerSocketChannel) key.channel();
					// Accepts connection and get SocketChannel.
					SocketChannel channel = server.accept();
					// Sets the SocketChannel to be non-blocking.
					channel.configureBlocking(false);

					// Sends message.
					channel.write(ByteBuffer.wrap("Echo: ".getBytes()));
					// Registers SocketChannel of connection with selector.
					channel.register(this.selector, SelectionKey.OP_READ);

				} else if (key.isReadable()) { // It's "read" event of SockectChannel.
					read(key);
				}
			}

		}
	}

	/**
	 * Read the data from the socket and send it right back.
	 * 
	 * @param key
	 * @throws IOException
	 */
	public void read(SelectionKey key) throws IOException {
		// Get socket channel from key.
		SocketChannel channel = (SocketChannel) key.channel();
		
		ByteBuffer echoBuffer = ByteBuffer.allocate(0x400);
		while (true) {
			echoBuffer.clear();
			int readCount = channel.read(echoBuffer);
			if (readCount == -1) {
				break;
			}
			
			echoBuffer.flip();
			channel.write(echoBuffer);
		}
		
		
		// Allocates a byte buffer.
		/*ByteBuffer inBuffer = ByteBuffer.allocate(10);
		channel.read(inBuffer);
		byte[] data = inBuffer.array();
		String msg = new String(data).trim();
		System.out.println("Message: " + msg);
		ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
		channel.write(outBuffer);*/
	}

	/**
	 * Entry of NIO server.
	 * 
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		NIOServer server = new NIOServer();
		server.initServer(8000);
		server.listen();
	}

}
